#!/bin/bash
#
# This script converts a Mavericks app bundle to a bootable dmg image file
# in a few steps:
# - Converts the BaseSystem.dmg image file to a writable format and resizes it
#   to contain additional data.
# - Replaces the Packages symlink with the Packages folder from the
#   InstallESD.dmg image file.
# - Adds the BaseSystem.dmg and BaseSystem.chunklist


source_app="$1"
boot_dmg="$2"

ERR_INVALID_ARG=1
ERR_FILE_EXISTS=2
ERR_UNEXPECTED=3

# Check input params
if [[ -z "$source_app" ]]; then
	echo "Please specify the Mavericks app bundle."
	exit $ERR_INVALID_ARG
fi
if [[ ! -r "$source_app" ]]; then
	echo "'$source_app' is not accessible."
	exit $ERR_INVALID_ARG
fi

if [[ -z "$boot_dmg" ]]; then
	echo "Please specify where to save the resulting dmg image file."
	exit $ERR_INVALID_ARG
fi
if [[ -e "$boot_dmg" ]]; then
	if [[ -n "$3" && "$3" == "--overwrite" ]]; then
		echo "The file '$boot_dmg' already exists and will be removed"
		rm "$boot_dmg" || exit $ERR_UNEXPECTED
	else	
		echo "The file '$boot_dmg' already exists. Please choose another \
filename and try again."
		exit $ERR_FILE_EXISTS
	fi
fi


temp_dir="$(mktemp -d -t 'Mavericks_Boot_Image')"

install_esd_mnt="${temp_dir}/install_esd"
boot_image_mnt="${temp_dir}/boot_image"
tmp_binaries="${temp_dir}/tmp_binaries"

install_esd_dmg="${source_app}/Contents/SharedSupport/InstallESD.dmg"
base_system_dmg="${install_esd_mnt}/BaseSystem.dmg"
base_system_chunklist="${install_esd_mnt}/BaseSystem.chunklist"
input_packages="${install_esd_mnt}/Packages"
output_packages="${boot_image_mnt}/System/Installation/Packages"

boot_image_ready="no"


set -x
trap "cleanup; exit $ERR_UNEXPECTED" ERR
trap "cleanup; exit $ERR_UNEXPECTED" SIGHUP SIGINT SIGTERM

terminate() {
	local PID=$1
	if [ -n "$PID" ]; then
		kill -SIGTERM "$PID"
		wait "$PID"
	fi
}

wait_for_process() {
	local PID=$1
	trap "terminate $PID; cleanup; exit $ERR_UNEXPECTED" SIGHUP SIGINT SIGTERM
	wait $PID
	res="$?"
	trap "cleanup; exit $ERR_UNEXPECTED" SIGHUP SIGINT SIGTERM
	echo "Wait finished with code $res"
	return $res
}

execute_in_background() {
	"$@" &
	local PID=$!
	wait_for_process $PID
}

cleanup() {
	# Wait for unfinished processes if script was cancelled before
	# trap for termination was set.
	wait

	umount_path "$install_esd_mnt"
	umount_path "$boot_image_mnt"

	rm -rf "$tmp_binaries"

	rmdir "$temp_dir"

	if [[ -e "$boot_dmg" && "$boot_image_ready" == "no" ]]; then
		rm "$boot_dmg"
	fi
}

mount_dmg() {
	local dmg="$1"
	local mnt="$2"

	execute_in_background hdiutil attach "$dmg" -mountpoint "$mnt" -nobrowse
	return $?
}

umount_path() {
	if [[ ! -d "$1" ]]; then
	   return
	fi

	for i in `seq 1 10`; do
		hdiutil detach "$1"
		if [[ $? == 0 ]]; then
			break
		fi
		sleep 1
	done
}

convert_dmg() {
	local src_dmg="$1"
	local dst_dmg="$2"

	execute_in_background hdiutil convert "$src_dmg" -o "$dst_dmg" -format UDRW
	return $?
}

get_size() {
	local path="$1"
	BLOCKSIZE=512 du -s "$path" | awk '{print $1}'
	return $?
}

get_dmg_size() {
	local dmg="$1"
	hdiutil resize -limits "$dmg" | awk '{print $2}'
	return $?
}

resize_dmg() {
	local dmg="$1"
	local size="$2"

	execute_in_background hdiutil resize "$dmg" -sectors "$size"
	return $?
}

extract_from_pkg() {
	local src_pkg="$1"
	local dst_path="$2"
	local file="$3"

	mkdir "$dst_path" || return 1
	( cd "$dst_path" && \
	  execute_in_background xar -xf "$src_pkg" -v --exclude Bom --exclude PackageInfo && \
	  execute_in_background bunzip2 -c Payload | cpio -id "./$3" ; \
	  rm Payload )

	[ -e "${dst_path}/${file}" ]
	return $?
}

mount_dmg "${install_esd_dmg}" "$install_esd_mnt"

convert_dmg "${base_system_dmg}" "${boot_dmg}"

current_size="$(get_dmg_size "${boot_dmg}")"
packages_size="$(get_size "${install_esd_mnt}/Packages")"
base_system_size="$(get_size "${base_system_dmg}")"
base_system_chunklist_size="$(get_size "${base_system_chunklist}")"
resize_dmg "${boot_dmg}" "$(( $current_size + $packages_size + $base_system_size + $base_system_chunklist_size))"

mount_dmg "${boot_dmg}" "${boot_image_mnt}"

rm "${output_packages}"
execute_in_background cp -r "${input_packages}" "${output_packages}"
execute_in_background cp -r "${base_system_dmg}" "${boot_image_mnt}"
cp -r "${base_system_chunklist}" "${boot_image_mnt}"

boot_image_ready="yes"

trap '' ERR
trap '' SIGHUP SIGINT SIGTERM
cleanup

stat -f "%z %N" "${boot_dmg}"
